#include "simple/support/iterator.hpp"

#include <vector>
#include <algorithm>
#include <cassert>

using namespace simple::support;

void Offset()
{
	std::vector x{10};
	offset_iterator ofi {x};
	while(*ofi != 0) x.push_back(*ofi++ - 1);
	assert(( x == std::vector{10,9,8,7,6,5,4,3,2,1,0} ));
}

void OffsetExpander()
{
	std::vector x{10};
	offset_expander ofe {x};
	offset_expander prev = ofe++;
	while(*prev != 0) *ofe++ = *prev++ - 1;
	assert(( x == std::vector{10,9,8,7,6,5,4,3,2,1,0} ));
}

void OutAccumulate()
{
	std::vector x{1,2,3,4,5,6,7,8,9,10};
	assert(( std::copy_if(x.begin(), x.end(), out_accumulate(0), [](auto x){ return x%2 == 0;} ).accumulator == 30 ));
	assert(( std::copy_if(x.begin(), x.end(), out_accumulate(0), [](auto x){ return x%2 == 1;} ).accumulator == 25 ));
}

void OutTransform()
{
	std::vector x{1,2,3,4,5,6,7,8,9,10};
	auto end = std::copy_if(x.begin(), x.end(),
		out_transform([](auto x){ return x + 2; }, x.begin()),
		[](auto x) { return x%3 == 0; }
	).out;
	std::vector y{5,8,11};
	assert(( std::equal(x.begin(), end, y.begin(), y.end()) ));
}

void OutFilter()
{
	// omg omg, is transform going to be applied twice, once for filter condition and once again for assignment??
	// do I need to cache the valueeeee omgggg?!
	// no... nothing of sorts... who hurt you so?

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		auto end = std::transform(x.begin(), x.end(),
			out_filter([](auto x){ return x%3 == 0; }, x.begin()),
			[](auto x){ return x + 2; }
		).out;
		std::vector y{3,6,9,12};
		assert(( std::equal(x.begin(), end, y.begin(), y.end()) ));
	}

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		auto end = std::copy(x.begin(), x.end(),
			out_transform([](auto x){ return x + 2; },
			out_filter([](auto x){ return x%3 == 0; }, x.begin()
		))).out.out;
		std::vector y{3,6,9,12};
		assert(( std::equal(x.begin(), end, y.begin(), y.end()) ));
	}

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		std::vector y{21,22};
		std::copy(x.begin(), x.end(),
			out_transform([](auto x){ return x + 2; },
			out_filter([](auto x){ return x%3 == 0; },
			offset_expander(y)
		)));
		assert(( y == std::vector{3,6,9,12} ));
	}
}

void OutFlattenRange()
{
	std::vector x {
		std::vector{0},
		std::vector{1,2},
		std::vector{3,4,5,6},
		std::vector{0},
		std::vector{0},
		std::vector{7},
		std::vector{8,9,10},
		std::vector{0}
	};
	std::vector y(10,0);

	std::copy_if(x.begin(), x.end(), out_flatten_range(y.begin()),
		[](auto& x) { return x != std::vector{0}; });

	assert(( y == std::vector{1,2,3,4,5,6,7,8,9,10} ));
}

void OutFlattenTuple()
{
	std::vector x {
		std::tuple{0,0,0},
		std::tuple{1,2,3},
		std::tuple{4,5,6},
		std::tuple{0,0,0},
		std::tuple{0,0,0},
		std::tuple{7,8,9},
		std::tuple{0,0,0},
	};
	std::vector y(9,0);

	std::copy_if(x.begin(), x.end(), out_flatten_tuple(y.begin()),
		[](auto& x) { return x != std::tuple{0,0,0}; });

	assert(( y == std::vector{1,2,3,4,5,6,7,8,9} ));
}

void OutInvoke()
{
	std::vector x {
		std::vector{1,2,3},
		std::vector{1,3,2},
		std::vector{1,2,3},
		std::vector{1,2,3},

		std::vector{3,1,2},
		std::vector{2,3,1},
		std::vector{1,2,3},
		std::vector{3,2,1}
	};

	std::copy_if(x.begin(), x.end(),
		out_invoke([](auto& x) { std::sort(x.begin(), x.end()); }),
		[](auto& x) { return not std::is_sorted(x.begin(), x.end()); }
	);

	assert(( x == std::vector{
		std::vector{1,2,3},
		std::vector{1,2,3},
		std::vector{1,2,3},
		std::vector{1,2,3},

		std::vector{1,2,3},
		std::vector{1,2,3},
		std::vector{1,2,3},
		std::vector{1,2,3}
	} ));
}

void OutOptional()
{
	const int a = 1, b = 2, c = 3, d = 4;
	std::vector<const int*> x{&a, nullptr, &b, &c, nullptr, nullptr, nullptr, &d};
	std::vector y(4,0);
	std::copy(x.begin(), x.end(), out_optional(y.begin()));
	assert(( y == std::vector{1,2,3,4} ));
}

void OutPartition()
{
	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		auto part = std::copy_if(x.begin(), x.end(),
			out_partition([](auto x) { return x%2 == 0; }, x.begin(), x.begin()),
			[](auto x) { return x > 3; }
		);
		std::vector y{4,6,8,10};
		assert(( std::equal(x.begin(), part.partition_point, y.begin(), y.end()) ));
		std::vector z{4,6,8,10, 5,9,7};
		assert(( std::equal(x.begin(), part.out, z.begin(), z.end()) ));
		assert(( x == std::vector{4,6,8,10, 5,9,7, 8,9,10} ));
	}

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		std::vector y{99,88};
		auto part = std::copy(x.begin(), x.end(),
			out_filter([](auto x) { return x > 3; },
			out_partition([](auto x) { return x%2 == 0; }, offset_iterator(y),
			out_transform([](auto x) { return x + x; },
			offset_expander(y)
		)))).out.partition_point.base();
		std::vector z{8,12,16,20};
		assert(( std::equal(y.begin(), part, z.begin(), z.end()) ));
		std::vector w{10,18,14};
		assert(( std::equal(part, y.end(), w.begin(), w.end()) ));
		assert(( y == std::vector{8,12,16,20, 10,18,14} ));
	}
}

void InFilter()
{
	// auto modern_copy = [](iterable auto begin, equality_comparable<decltype(begin)> auto end, iterable out) ...
	auto modern_copy = [](auto begin, auto end, auto out) { for(; begin != end; ++begin, ++out) *out = *begin; };

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		std::vector<int> y;
		modern_copy(in_filter([](auto x){ return x>3; }, x), in_filter_end{}, std::back_inserter(y));
		assert(( y == std::vector{4,5,6,7,8,9,10} ));
	}

	{
		std::vector x{1,2,3,4,5,6,7,8,9,10};
		std::vector<int> y;
		modern_copy(in_filter([](auto x){ return x%3 == 0; }, x), in_filter_end{}, std::back_inserter(y));
		assert(( y == std::vector{3,6,9} ));
	}
}

int main()
{
	Offset();
	OffsetExpander();
	OutAccumulate();
	OutTransform();
	OutFilter();
	OutFlattenRange();
	OutFlattenTuple();
	OutInvoke();
	OutOptional();
	OutPartition();
	InFilter();

	return 0;
}
